/*
REWRITTEN FROM SCRATCH BY XINEF, IT OWNS NOW!
*/

#include "ScriptMgr.h"
#include "ScriptedCreature.h"
#include "CreatureTextMgr.h"
#include "culling_of_stratholme.h"
#include "Player.h"
#include "TemporarySummon.h"
#include "SpellInfo.h"

class instance_culling_of_stratholme : public InstanceMapScript
{
    public:
    instance_culling_of_stratholme() : InstanceMapScript("instance_culling_of_stratholme", 595) { }

    InstanceScript* GetInstanceScript(InstanceMap* pMap) const
    {
        return new instance_culling_of_stratholme_InstanceMapScript(pMap);
    }

    struct instance_culling_of_stratholme_InstanceMapScript : public InstanceScript
    {
        instance_culling_of_stratholme_InstanceMapScript(Map* pMap) : InstanceScript(pMap)
        {
			// NPCs
            _arthasGUID = 0;
			_infiniteGUID = 0;

			// GOs
            _shkafGateGUID = 0;
            _exitGateGUID = 0;
            
			// Instance
            _crateCount = 0;
			_showCrateTimer = 0;
			_guardianTimer = 0;
			_respawnAndReposition = false;
			_encounterState = COS_PROGRESS_NOT_STARTED;
			_loadTimer = 0;
        }

        bool IsEncounterInProgress() const
        {
            return false;
        }

        void FillInitialWorldStates(WorldPacket& data)
        {
            data << uint32(WORLDSTATE_SHOW_CRATES) << uint32(0);
            data << uint32(WORLDSTATE_CRATES_REVEALED) << uint32(_crateCount);
            data << uint32(WORLDSTATE_WAVE_COUNT) << uint32(0);
            data << uint32(WORLDSTATE_TIME_GUARDIAN) << uint32(25);
            data << uint32(WORLDSTATE_TIME_GUARDIAN_SHOW) << uint32(0);
        }

		void OnPlayerEnter(Player* plr)
		{
			if (instance->GetPlayersCountExceptGMs() == 1)
				SetData(DATA_ARTHAS_REPOSITION, 2);

			EnsureGridLoaded();

			if (plr->getRace() != RACE_HUMAN && plr->getRace() != RACE_DWARF && plr->getRace() != RACE_GNOME)
				plr->CastSpell(plr, ((plr->getGender() == GENDER_MALE) ? SPELL_HUMAN_MALE : SPELL_HUMAN_FEMALE), true);
		}

        void OnCreatureCreate(Creature* creature)
        {
            switch (creature->GetEntry())
            {
                case NPC_ARTHAS:
                    _arthasGUID = creature->GetGUID();
					if (_encounterState == COS_PROGRESS_FINISHED)
						creature->SetVisible(false);
					else
						Reposition(creature);
                    break;
				case NPC_INFINITE:
					_infiniteGUID = creature->GetGUID();
					break;
            }
        }

        void OnGameObjectCreate(GameObject* go)
        {
            switch (go->GetEntry())
            {
                case GO_SHKAF_GATE:
                    _shkafGateGUID = go->GetGUID();
					if (_encounterState >= COS_PROGRESS_KILLED_EPOCH)
						go->SetGoState(GO_STATE_ACTIVE);
                    break;
                case GO_EXIT_GATE:
                    _exitGateGUID = go->GetGUID();
                    if (_encounterState == COS_PROGRESS_FINISHED)
                        go->SetGoState(GO_STATE_ACTIVE);
                    break;
            }
        }

        void SetData(uint32 type, uint32 data)
        {
            switch (type)
            {
				case DATA_SHOW_CRATES:
					DoUpdateWorldState(WORLDSTATE_SHOW_CRATES, data);
					return;
				case DATA_SHOW_INFINITE_TIMER:
					if (!instance->IsHeroic() || !_guardianTimer)
						return;
					DoUpdateWorldState(WORLDSTATE_TIME_GUARDIAN_SHOW, data);
					DoUpdateWorldState(WORLDSTATE_TIME_GUARDIAN, uint32(_guardianTimer / (MINUTE*IN_MILLISECONDS)));
					if (data == 0)
					{
						_guardianTimer = 0;
						SaveToDB();
					}
					else if (!_infiniteGUID)
						instance->SummonCreature(NPC_INFINITE, EventPos[EVENT_SRC_CORRUPTOR]);
					return;
				case DATA_START_WAVES:
					DoUpdateWorldState(WORLDSTATE_WAVE_COUNT, 1);
					if (instance->IsHeroic())
					{
						DoUpdateWorldState(WORLDSTATE_TIME_GUARDIAN_SHOW, true);
						_guardianTimer = 26*MINUTE*IN_MILLISECONDS;
						if (!_infiniteGUID)
							instance->SummonCreature(NPC_INFINITE, EventPos[EVENT_SRC_CORRUPTOR]);
					}
					return;
				case DATA_CRATE_COUNT:
                    _crateCount++;
                    if (_crateCount == 5)
                    {
						Map::PlayerList const &PlayerList = instance->GetPlayers();
						if (!PlayerList.isEmpty())
							for (Map::PlayerList::const_iterator i = PlayerList.begin(); i != PlayerList.end(); ++i)
								i->GetSource()->KilledMonsterCredit(NPC_GRAIN_CREATE_TRIGGER, 0);

						_showCrateTimer++;
						if (GetData(DATA_ARTHAS_EVENT) < COS_PROGRESS_CRATES_FOUND)
							SetData(DATA_ARTHAS_EVENT, COS_PROGRESS_CRATES_FOUND);
                    }

                    DoUpdateWorldState(WORLDSTATE_CRATES_REVEALED, _crateCount);
                    return;
				case DATA_ARTHAS_EVENT:
					// Start Event
					_encounterState = data;
					if (data == COS_PROGRESS_START_INTRO)
					{
						if (Creature *arthas = instance->GetCreature(_arthasGUID))
							arthas->AI()->DoAction(ACTION_START_EVENT);
					}
					else if (data == COS_PROGRESS_KILLED_SALRAMM)
					{
						if (Creature *arthas = instance->GetCreature(_arthasGUID))
							arthas->AI()->DoAction(ACTION_KILLED_SALRAMM);
					}
					break;
				case DATA_ARTHAS_REPOSITION:
					if (data == 2)
						_respawnAndReposition = true;
					else if (Creature *arthas = instance->GetCreature(_arthasGUID))
						Reposition(arthas);
					return;

            }

            if (type == DATA_ARTHAS_EVENT)
                SaveToDB();
        }

        uint32 GetData(uint32 type) const
        {
            switch (type)
            {
				case DATA_ARTHAS_EVENT:
					return _encounterState;
				case DATA_GUARDIANTIME_EVENT:
					return _guardianTimer;
            }
            return 0;
        }

        uint64 GetData64(uint32 identifier) const
        {
            switch (identifier)
            {
				case DATA_SHKAF_GATE:
					return _shkafGateGUID;
				case DATA_ARTHAS:
					return _arthasGUID;
				case DATA_EXIT_GATE:
					return _exitGateGUID;
            }
            return 0;
        }

		void Update(uint32 diff)
		{
			if (_loadTimer)
			{
				_loadTimer += diff;
				if (_loadTimer > 3000)
				{
					UpdateEventState();
					_loadTimer = 0;
				}
			}
			// Used when arthas dies
			if (_respawnAndReposition)
			{
				if (Creature *arthas = instance->GetCreature(_arthasGUID))
				{
					if (!arthas->IsAlive())
					{
						EnsureGridLoaded();
						arthas->setDeathState(DEAD);
						arthas->Respawn();
					}
					else
					{
						arthas->AI()->Reset();
						_respawnAndReposition = false;
					}
				}
			}

			// Used after 5-th crates is revealed
			if (_showCrateTimer)
			{
				_showCrateTimer += diff;
				if (_showCrateTimer >= 20000)
				{
				    UpdateEventState();
					_showCrateTimer = 0; // just to be sure
				}
			}

			// Used to display how much time players have
			if (_guardianTimer)
			{
				uint32 div = uint32(_guardianTimer / (MINUTE*IN_MILLISECONDS));
				_guardianTimer -= diff;
				uint32 divAfter = uint32(_guardianTimer / (MINUTE*IN_MILLISECONDS));
				
				if (divAfter == 0)
				{
					_guardianTimer = 0;
					DoUpdateWorldState(WORLDSTATE_TIME_GUARDIAN_SHOW, 0);

					// Inform infinite we run out of time
					if (instance->IsHeroic() && _infiniteGUID)
						if (Creature* cr = instance->GetCreature(_infiniteGUID))
							cr->AI()->DoAction(ACTION_RUN_OUT_OF_TIME);

				}
				else if (div > divAfter)
				{
					if (divAfter == 5)
						ChromieWhisper(1);
					else if (divAfter == 1)
						ChromieWhisper(2);

					DoUpdateWorldState(WORLDSTATE_TIME_GUARDIAN, divAfter);
					SaveToDB();
				}
			}
		}

		void UpdateEventState()
		{
			if (_encounterState > COS_PROGRESS_NOT_STARTED)
			{
				// Summon Chromie and global whisper
                instance->SummonCreature(NPC_CHROMIE_MIDDLE, EventPos[EVENT_POS_CHROMIE]);
				instance->SummonCreature(NPC_HOURGLASS, EventPos[EVENT_POS_HOURGLASS]);

				if (_encounterState == COS_PROGRESS_CRATES_FOUND ||
					_encounterState == COS_PROGRESS_START_INTRO)
				{
					ChromieWhisper(0);

					// hide crates count
					DoUpdateWorldState(WORLDSTATE_SHOW_CRATES, 0);
					_showCrateTimer = 0;
					_encounterState = COS_PROGRESS_CRATES_FOUND;
				}
			}
		}

		void ChromieWhisper(uint8 textId)
		{
			if (!instance->GetPlayers().isEmpty())
				if (Player* player = instance->GetPlayers().getFirst()->GetSource())
				{
					Position pos;
					player->GetPosition(&pos);
					if (Creature* cr = instance->SummonCreature(NPC_CHROMIE_MIDDLE, pos))
					{
						cr->SetVisible(false);
						cr->DespawnOrUnsummon(1000);
						sCreatureTextMgr->SendChat(cr, textId, player, CHAT_MSG_ADDON, LANG_ADDON, TEXT_RANGE_MAP);
					}
				}
		}

		void Reposition(Creature* arthas)
		{
			switch (GetData(DATA_ARTHAS_EVENT))
			{
				case COS_PROGRESS_FINISHED_INTRO:
					arthas->UpdatePosition(LeaderIntroPos2, true);
					arthas->SetHomePosition(LeaderIntroPos2);
					arthas->SetFacingTo(LeaderIntroPos2.GetOrientation());
					break;
				case COS_PROGRESS_FINISHED_CITY_INTRO:
				case COS_PROGRESS_KILLED_MEATHOOK:
				case COS_PROGRESS_KILLED_SALRAMM:
					arthas->UpdatePosition(LeaderIntroPos2special, true);
					arthas->SetHomePosition(LeaderIntroPos2special);
					arthas->SetFacingTo(LeaderIntroPos2special.GetOrientation());
					break;
				case COS_PROGRESS_REACHED_TOWN_HALL:
					arthas->UpdatePosition(LeaderIntroPos3, true);
					arthas->SetHomePosition(LeaderIntroPos3);
					arthas->SetFacingTo(LeaderIntroPos3.GetOrientation());
					break;
				case COS_PROGRESS_KILLED_EPOCH:
					arthas->UpdatePosition(LeaderIntroPos4, true);
					arthas->SetHomePosition(LeaderIntroPos4);
					arthas->SetFacingTo(LeaderIntroPos4.GetOrientation());
					break;
				case COS_PROGRESS_LAST_CITY:
					arthas->UpdatePosition(LeaderIntroPos5, true);
					arthas->SetHomePosition(LeaderIntroPos5);
					arthas->SetFacingTo(LeaderIntroPos5.GetOrientation());
					break;
				case COS_PROGRESS_BEFORE_MALGANIS:
					arthas->UpdatePosition(LeaderIntroPos6, true);
					arthas->SetHomePosition(LeaderIntroPos6);
					arthas->SetFacingTo(LeaderIntroPos6.GetOrientation());
					break;
			}
		}

		void EnsureGridLoaded()
		{
			instance->LoadGrid(LeaderIntroPos1.GetPositionX(), LeaderIntroPos1.GetPositionY());
			instance->LoadGrid(LeaderIntroPos2.GetPositionX(), LeaderIntroPos2.GetPositionY());
			instance->LoadGrid(LeaderIntroPos3.GetPositionX(), LeaderIntroPos3.GetPositionY());
			instance->LoadGrid(LeaderIntroPos4.GetPositionX(), LeaderIntroPos4.GetPositionY());
			instance->LoadGrid(LeaderIntroPos5.GetPositionX(), LeaderIntroPos5.GetPositionY());
			instance->LoadGrid(LeaderIntroPos6.GetPositionX(), LeaderIntroPos6.GetPositionY());
		}

		std::string GetSaveData()
        {
            OUT_SAVE_INST_DATA;

            std::ostringstream saveStream;
                saveStream << "C S " << _encounterState << ' ' << _guardianTimer;

            OUT_SAVE_INST_DATA_COMPLETE;
            return saveStream.str();
        }

        void Load(const char* in)
        {
            if (!in)
            {
                OUT_LOAD_INST_DATA_FAIL;
                return;
            }

            OUT_LOAD_INST_DATA(in);

            char dataHead1, dataHead2;
            uint32 data0, data1;

            std::istringstream loadStream(in);
            loadStream >> dataHead1 >> dataHead2 >> data0 >> data1;

            if (dataHead1 == 'C' && dataHead2 == 'S')
            {
                _encounterState = data0;
				_guardianTimer = data1;

				//UpdateEventState();
				_loadTimer++;
            }
            else
                OUT_LOAD_INST_DATA_FAIL;

            OUT_LOAD_INST_DATA_COMPLETE;
        }

        private:
			// NPCs
            uint64 _arthasGUID;
			uint64 _infiniteGUID;

			// GOs
            uint64 _shkafGateGUID;
            uint64 _exitGateGUID;
            uint32 _encounterState;
            uint32 _crateCount;
			uint32 _showCrateTimer;
			uint32 _guardianTimer;

			bool _respawnAndReposition;
			uint32 _loadTimer;
    };
};

void AddSC_instance_culling_of_stratholme()
{
    new instance_culling_of_stratholme();
}
